import logging
import os
import signal
import subprocess
import threading
import time
from pathlib import Path
from typing import Dict, List, Optional
from datetime import datetime, timedelta

import psutil
import requests
from django.conf import settings
from .monitoring import send_battle_alert, AlertType, AlertLevel

logger = logging.getLogger(__name__)


class NodeServerError(Exception):
    """Node服务器相关异常"""
    pass


class PortLease:
    """端口租约类 - 实现端口独占机制"""

    def __init__(self, port: int, battle_id: str, lease_duration: int = 3600):
        self.port = port
        self.battle_id = battle_id
        self.lease_start = datetime.now()
        self.lease_duration = timedelta(seconds=lease_duration)
        self.is_active = True

    @property
    def is_expired(self) -> bool:
        """检查租约是否过期"""
        return datetime.now() > (self.lease_start + self.lease_duration)

    @property
    def remaining_time(self) -> int:
        """获取剩余租约时间（秒）"""
        remaining = (self.lease_start + self.lease_duration) - datetime.now()
        return max(0, int(remaining.total_seconds()))

    def renew(self, duration: int = None):
        """续租"""
        if duration is None:
            from django.conf import settings
            duration = settings.NODE_SERVER_MANAGER.get('PORT_LEASE_DURATION', 3600)
        self.lease_start = datetime.now()
        self.lease_duration = timedelta(seconds=duration)
        self.is_active = True

    def release(self):
        """释放租约"""
        self.is_active = False


class NodeServerConfig:
    """Node服务器配置管理"""

    def __init__(self):
        self.startup_timeout = settings.NODE_SERVER_MANAGER.get('STARTUP_TIMEOUT', 20)
        self.shutdown_timeout = settings.NODE_SERVER_MANAGER.get('SHUTDOWN_TIMEOUT', 10)
        self.health_check_timeout = settings.NODE_SERVER_MANAGER.get('HEALTH_CHECK_TIMEOUT', 3)
        self.port_range = settings.NODE_SERVER_MANAGER.get('PORT_RANGE', (3000, 3999))
        self.max_servers = settings.NODE_SERVER_MANAGER.get('MAX_INSTANCES', 50)
        self.battle_server_path = self._get_battle_server_path()
        # 新增配置
        self.log_monitoring_enabled = settings.NODE_SERVER_MANAGER.get('LOG_MONITORING_ENABLED', False)
        self.process_check_interval = settings.NODE_SERVER_MANAGER.get('PROCESS_CHECK_INTERVAL', 0.5)
        # 端口租约配置
        self.port_lease_duration = settings.NODE_SERVER_MANAGER.get('PORT_LEASE_DURATION', 3600)
        self.port_lease_cleanup_interval = settings.NODE_SERVER_MANAGER.get('PORT_LEASE_CLEANUP_INTERVAL', 300)

    def _get_battle_server_path(self) -> str:
        """获取battle_server目录路径"""
        # 优先使用配置中的路径
        if hasattr(settings, 'BATTLE_SERVER_PATH'):
            path = Path(settings.BATTLE_SERVER_PATH)
            if path.exists():
                return str(path)

        # 从Django项目根目录向上一级，然后进入battle_server
        django_root = Path(settings.BASE_DIR)
        battle_server_path = django_root.parent / 'battle_server'

        # 如果上级目录没有battle_server，检查当前目录
        if not battle_server_path.exists():
            battle_server_path = django_root / 'battle_server'

        if not battle_server_path.exists():
            raise NodeServerError(f'Battle server directory not found: {battle_server_path}')

        return str(battle_server_path)


# 全局配置实例
node_config = NodeServerConfig()


class NodeBattleServer:
    """单个Node.js战斗服务器实例"""

    def __init__(self, battle_id: str, port: int, config: dict = None):
        self.battle_id = battle_id
        self.port = port
        self.config = config or {}
        self.process: Optional[subprocess.Popen] = None
        self.status = 'stopped'  # stopped, starting, running, stopping, error
        self.start_time = None
        self.error_message = None
        self._log_thread = None
        self._shutdown_event = threading.Event()
        self._log_file_path = None

        # 验证参数
        if not battle_id or not isinstance(battle_id, str):
            raise NodeServerError('battle_id必须是非空字符串')
        if not isinstance(port, int) or port <= 0:
            raise NodeServerError('port必须是正整数')
        if not (1024 <= port <= 65535):
            raise NodeServerError('port必须在1024-65535范围内')

    def start(self) -> bool:
        """启动Node.js服务器"""
        if self.status == 'running':
            logger.warning(f'Battle server {self.battle_id} is already running')
            return True

        try:
            self.status = 'starting'
            self.error_message = None
            self._shutdown_event.clear()

            # 检查端口是否被占用（使用更严格的检查）
            if not self._is_port_available_for_start(self.port):
                raise NodeServerError(f'Port {self.port} is already in use')

            # 检查Node.js和npm是否可用
            if not self._check_node_available():
                raise NodeServerError('Node.js is not available')

            # 安装依赖（如果需要）
            self._ensure_dependencies()

            # 启动Node.js服务器
            cmd = ['node', 'server.js', str(self.port), self.battle_id]

            logger.info(f'Starting battle server {self.battle_id} on port {self.port}')
            logger.debug(f'Command: {" ".join(cmd)}')
            logger.debug(f'Working directory: {node_config.battle_server_path}')

            # 设置环境变量
            env = os.environ.copy()
            env['NODE_ENV'] = 'production'
            env['BATTLE_ID'] = self.battle_id

            # 创建日志文件路径，解决I/O管道阻塞问题
            logs_dir = Path(node_config.battle_server_path).parent / 'logs'
            logs_dir.mkdir(exist_ok=True)
            self._log_file_path = logs_dir / f'battle_server_{self.battle_id}.log'

            # 打开日志文件（正常日志和错误日志使用同一个文件）
            log_file = open(self._log_file_path, 'w', encoding='utf-8')

            self.process = subprocess.Popen(
                cmd,
                cwd=node_config.battle_server_path,
                stdout=log_file,  # 重定向到文件，避免管道阻塞
                stderr=log_file,  # 重定向到文件，避免管道阻塞
                text=True,
                env=env,
                # preexec_fn=os.setsid if os.name != 'nt' else None,  # 创建新的进程组, 父子进程脱钩
            )

            # 保存文件句柄以便后续关闭
            self._log_file = log_file

            # 等待服务器启动
            if self._wait_for_startup():
                self.status = 'running'
                self.start_time = time.time()
                logger.info(f'Battle server {self.battle_id} started successfully')

                # 发送服务器启动成功告警
                send_battle_alert(
                    alert_type=AlertType.SERVER_STARTUP_SUCCESS,
                    level=AlertLevel.INFO,
                    message=f"战斗服务器 {self.battle_id} 启动成功",
                    battle_id=self.battle_id,
                    port=self.port,
                    metadata={'startup_time': time.time() - self.start_time}
                )

                # 启动日志监控线程（如果启用）
                if node_config.log_monitoring_enabled:
                    self._start_log_monitoring()

                return True
            else:
                self.status = 'error'
                self.error_message = 'Failed to start within timeout'

                # 发送服务器启动超时告警
                send_battle_alert(
                    alert_type=AlertType.SERVER_STARTUP_FAILURE,
                    level=AlertLevel.ERROR,
                    message=f"战斗服务器 {self.battle_id} 启动超时",
                    battle_id=self.battle_id,
                    port=self.port,
                    metadata={'error': 'startup timeout'}
                )

                self._cleanup_process()
                return False

        except NodeServerError as e:
            # 发送Node服务器错误告警
            send_battle_alert(
                alert_type=AlertType.SERVER_STARTUP_FAILURE,
                level=AlertLevel.ERROR,
                message=f"战斗服务器 {self.battle_id} 启动失败: {str(e)}",
                battle_id=self.battle_id,
                port=self.port,
                metadata={'error': str(e)}
            )
            raise
        except Exception as e:
            self.status = 'error'
            self.error_message = str(e)
            logger.error(f'Failed to start battle server {self.battle_id}: {e}')

            # 发送服务器启动异常告警
            send_battle_alert(
                alert_type=AlertType.SERVER_STARTUP_FAILURE,
                level=AlertLevel.CRITICAL,
                message=f"战斗服务器 {self.battle_id} 启动异常: {str(e)}",
                battle_id=self.battle_id,
                port=self.port,
                metadata={'error': str(e)}
            )

            self._cleanup_process()
            raise NodeServerError(f'启动服务器失败: {e}')

    def stop(self) -> bool:
        """停止Node.js服务器"""
        if self.status == 'stopped':
            return True

        try:
            self.status = 'stopping'
            self._shutdown_event.set()  # 通知日志线程停止
            logger.info(f'Stopping battle server {self.battle_id}')

            if self.process:
                # 尝试优雅关闭
                try:
                    self.process.terminate()
                    if os.name != 'nt':
                        # Unix/Linux
                        os.kill(self.process.pid, signal.SIGTERM)
                except (ProcessLookupError, OSError) as e:
                    logger.debug(f'Process already terminated: {e}')

                # 等待进程结束
                try:
                    self.process.wait(timeout=node_config.shutdown_timeout)
                    logger.debug(f'Process {self.process.pid} terminated gracefully')
                except subprocess.TimeoutExpired:
                    # 强制杀死进程
                    logger.warning(f'Force killing battle server {self.battle_id}')
                    try:
                        self.process.terminate()
                        if os.name != 'nt':
                            # Unix/Linux
                            os.kill(self.process.pid, signal.SIGTERM)
                        self.process.wait(timeout=5)
                    except (ProcessLookupError, OSError, subprocess.TimeoutExpired) as e:
                        logger.error(f'Failed to kill process: {e}')

                self.process = None

            # 等待日志线程结束
            if self._log_thread and self._log_thread.is_alive():
                self._log_thread.join(timeout=2)

            self.status = 'stopped'
            logger.info(f'Battle server {self.battle_id} stopped')
            return True

        except Exception as e:
            logger.error(f'Error stopping battle server {self.battle_id}: {e}')
            self.status = 'error'
            self.error_message = str(e)
            return False

    def is_healthy(self) -> bool:
        """检查服务器是否健康 - 增强版"""
        if self.status != 'running' or not self.process:
            return False

        # 检查进程是否还在运行
        try:
            if self.process.poll() is not None:
                exit_code = self.process.returncode
                self.status = 'error'
                self.error_message = f'Process terminated unexpectedly with exit code {exit_code}'
                logger.warning(f'Battle server {self.battle_id} process died with exit code {exit_code}')

                # 发送进程异常退出告警
                send_battle_alert(
                    alert_type=AlertType.SERVER_CRASH,
                    level=AlertLevel.CRITICAL,
                    message=f"战斗服务器 {self.battle_id} 进程异常退出，退出码: {exit_code}",
                    battle_id=self.battle_id,
                    port=self.port,
                    metadata={'exit_code': exit_code}
                )

                return False
        except Exception as e:
            logger.error(f'Error checking process status for {self.battle_id}: {e}')
            return False

        # 检查进程资源使用情况（可选）
        try:
            if hasattr(self.process, 'pid'):
                proc = psutil.Process(self.process.pid)
                # 检查CPU使用率（如果过高可能有问题）
                cpu_percent = proc.cpu_percent()
                memory_info = proc.memory_info()

                # 记录资源使用情况（调试用）
                logger.debug(
                    f'Battle {self.battle_id} resource usage: CPU={cpu_percent:.1f}%, Memory={memory_info.rss / 1024 / 1024:.1f}MB'
                )

                # 如果内存使用超过配置限制，可能有内存泄漏
                memory_limit_bytes = settings.NODE_SERVER_MANAGER.get('MEMORY_LIMIT_MB', 500) * 1024 * 1024
                if memory_info.rss > memory_limit_bytes:
                    memory_mb = memory_info.rss / 1024 / 1024
                    logger.warning(f'Battle server {self.battle_id} using high memory: {memory_mb:.1f}MB')

                    # 发送高内存使用告警
                    send_battle_alert(
                        alert_type=AlertType.RESOURCE_USAGE,
                        level=AlertLevel.WARNING,
                        message=f"战斗服务器 {self.battle_id} 内存使用过高: {memory_mb:.1f}MB",
                        battle_id=self.battle_id,
                        port=self.port,
                        metadata={
                            'memory_mb': memory_mb,
                            'cpu_percent': cpu_percent
                        }
                    )
        except (psutil.NoSuchProcess, psutil.AccessDenied, AttributeError):
            # 进程可能已经结束或无权限访问，继续其他检查
            pass
        except Exception as e:
            logger.debug(f'Error checking process resources for {self.battle_id}: {e}')

        # 检查HTTP健康接口
        try:
            response = requests.get(f'http://localhost:{self.port}/health', timeout=node_config.health_check_timeout)
            if response.status_code == 200:
                return True
            else:
                logger.debug(f'Health check failed for {self.battle_id} with status {response.status_code}')
                self.error_message = f'Health check failed: HTTP {response.status_code}'
                return False
        except requests.exceptions.Timeout:
            logger.debug(f'Health check timeout for {self.battle_id}')
            self.error_message = 'Health check timeout'
            return False
        except requests.exceptions.ConnectionError:
            logger.debug(f'Health check connection error for {self.battle_id}')
            self.error_message = 'Health check connection failed'
            return False
        except requests.RequestException as e:
            logger.debug(f'Health check request failed for {self.battle_id}: {e}')
            self.error_message = f'Health check failed: {str(e)}'
            return False

    def get_status(self) -> dict:
        """获取服务器状态信息"""
        status_info = {
            'battle_id': self.battle_id,
            'port': self.port,
            'status': self.status,
            'start_time': self.start_time,
            'uptime': time.time() - self.start_time if self.start_time else 0,
            'error_message': self.error_message,
            'healthy': self.is_healthy(),
            'pid': self.process.pid if self.process else None,
        }

        # 添加进程信息
        if self.process and self.process.pid:
            try:
                proc = psutil.Process(self.process.pid)
                status_info.update(
                    {
                        'memory_usage': proc.memory_info().rss / 1024 / 1024,  # MB
                        'cpu_percent': proc.cpu_percent(),
                        'create_time': proc.create_time(),
                    }
                )
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                pass

        # 尝试获取详细状态
        if self.status == 'running':
            try:
                response = requests.get(f'http://localhost:{self.port}/battle/status', timeout=node_config.health_check_timeout)
                if response.status_code == 200:
                    status_info['battle_status'] = response.json()
            except requests.RequestException as e:
                logger.debug(f'Failed to get battle status: {e}')

        return status_info

    def _is_port_in_use(self, port: int) -> bool:
        """检查端口是否被占用"""
        try:
            import socket

            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.settimeout(1)
                result = sock.connect_ex(('localhost', port))
                return result == 0
        except Exception:
            return False

    def _is_port_available_for_start(self, port: int) -> bool:
        """检查端口是否可用于启动服务器"""
        import socket

        # 方法1: 尝试绑定端口
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                sock.bind(('localhost', port))
                sock.listen(1)
                # 端口可以绑定，说明可用
        except OSError:
            logger.debug(f'端口 {port} 绑定失败，被占用')
            return False

        # 方法2: 检查是否有进程在监听该端口
        try:
            import psutil
            for conn in psutil.net_connections():
                if conn.laddr.port == port and conn.status == psutil.CONN_LISTEN:
                    logger.debug(f'端口 {port} 被进程 {conn.pid} 监听')
                    return False
        except Exception as e:
            logger.debug(f'检查端口 {port} 监听状态时出错: {e}')

        return True

    def _check_node_available(self) -> bool:
        """检查Node.js是否可用"""
        try:
            result = subprocess.run(['node', '--version'], capture_output=True, text=True, timeout=10)
            if result.returncode == 0:
                logger.debug(f'Node.js version: {result.stdout.strip()}')
                return True
            else:
                logger.error(f'Node.js check failed: {result.stderr}')
                return False
        except FileNotFoundError:
            logger.error('Node.js not found in PATH')
            return False
        except subprocess.TimeoutExpired:
            logger.error('Node.js version check timed out')
            return False
        except Exception as e:
            logger.error(f'Error checking Node.js: {e}')
            return False

    def _ensure_dependencies(self):
        """确保Node.js依赖已安装"""
        package_json_path = os.path.join(node_config.battle_server_path, 'package.json')
        node_modules_path = os.path.join(node_config.battle_server_path, 'node_modules')

        if not os.path.exists(package_json_path):
            raise NodeServerError(f'package.json not found in {node_config.battle_server_path}')

        if not os.path.exists(node_modules_path):
            logger.info('Installing Node.js dependencies...')
            try:
                result = subprocess.run(
                    ['npm', 'install'], cwd=node_config.battle_server_path, capture_output=True, text=True, timeout=120
                )

                if result.returncode != 0:
                    raise NodeServerError(f'Failed to install dependencies: {result.stderr}')

                logger.info('Dependencies installed successfully')
            except subprocess.TimeoutExpired:
                raise NodeServerError('npm install timed out')
            except FileNotFoundError:
                raise NodeServerError('npm not found in PATH')

    def _wait_for_startup(self) -> bool:
        """等待服务器启动 - 增强健康检查"""
        start_time = time.time()
        timeout = node_config.startup_timeout
        check_interval = settings.NODE_SERVER_MANAGER.get('STARTUP_CHECK_INTERVAL', 0.5)

        logger.info(f'Waiting for server {self.battle_id} to start (timeout: {timeout}s)')

        while time.time() - start_time < timeout:
            # 检查进程状态
            if self.process and self.process.poll() is not None:
                # 进程已经退出，检查退出码
                exit_code = self.process.returncode
                if exit_code != 0:
                    logger.error(f'Process for battle {self.battle_id} exited with code {exit_code}')
                    # 尝试读取日志文件
                    if self._log_file_path and self._log_file_path.exists():
                        try:
                            with open(self._log_file_path, 'r', encoding='utf-8') as f:
                                log_content = f.read().strip()
                                if log_content:
                                    logger.error(f'Log content: {log_content}')
                        except Exception as e:
                            logger.error(f'Failed to read log file: {e}')
                return False

            # 健康检查
            try:
                timeout = settings.NODE_SERVER_MANAGER.get('HEALTH_REQUEST_TIMEOUT', 3)
                response = requests.get(
                    f'http://localhost:{self.port}/health',
                    timeout=timeout,
                )
                if response.status_code == 200:
                    elapsed = time.time() - start_time
                    logger.info(f'Server {self.battle_id} started successfully in {elapsed:.2f}s')
                    return True
            except requests.RequestException as e:
                logger.debug(f'Health check failed for {self.battle_id}: {e}')
                pass

            time.sleep(check_interval)

        logger.error(f'Server {self.battle_id} failed to start within {timeout}s')
        return False

    def _start_log_monitoring(self):
        """启动日志监控线程 - 监控日志文件"""

        def monitor_logs():
            if not self._log_file_path or not self.process:
                return

            try:
                # 等待日志文件创建
                max_wait = 5
                wait_time = 0
                while not self._log_file_path.exists() and wait_time < max_wait:
                    time.sleep(0.1)
                    wait_time += 0.1

                if not self._log_file_path.exists():
                    logger.warning(f'Log file not found for battle {self.battle_id}')
                    return

                # 监控日志文件
                with open(self._log_file_path, 'r', encoding='utf-8') as f:
                    # 移动到文件末尾
                    f.seek(0, 2)

                    while not self._shutdown_event.is_set():
                        if self.process.poll() is not None:
                            # 进程已结束，读取剩余日志后退出
                            remaining_lines = f.readlines()
                            for line in remaining_lines:
                                logger.info(f'[Battle {self.battle_id}] {line.strip()}')
                            break

                        line = f.readline()
                        if line:
                            logger.info(f'[Battle {self.battle_id}] {line.strip()}')
                        else:
                            time.sleep(0.1)  # 避免CPU占用过高

            except Exception as e:
                logger.error(f'Log monitoring error for {self.battle_id}: {e}')

        self._log_thread = threading.Thread(target=monitor_logs, daemon=True)
        self._log_thread.start()

    def _cleanup_process(self):
        """清理进程资源"""
        # 停止日志监控
        self._shutdown_event.set()
        if self._log_thread and self._log_thread.is_alive():
            self._log_thread.join(timeout=2)

        # 关闭文件句柄
        try:
            if hasattr(self, '_log_file') and self._log_file:
                self._log_file.close()
        except Exception as e:
            logger.error(f'Error closing log files for {self.battle_id}: {e}')

        if self.process:
            try:
                if self.process.poll() is None:
                    self.process.terminate()
                    self.process.wait(timeout=5)
            except Exception as e:
                logger.debug(f'Error during process cleanup: {e}')
            finally:
                self.process = None


class NodeServerManager:
    """Node.js战斗服务器管理器"""

    def __init__(self):
        self.servers: Dict[str, NodeBattleServer] = {}
        self.used_ports = set()
        self._lock = threading.RLock()  # 线程安全锁

        # 端口租约系统
        self.port_leases: Dict[int, PortLease] = {}
        self._lease_lock = threading.RLock()
        self._last_lease_cleanup = datetime.now()

        # 启动时清理残留的Node.js战斗服务器进程
        self._cleanup_orphaned_processes()

        # 清理过期的端口租约
        self._cleanup_expired_leases()

        # 验证配置
        if len(self.servers) > node_config.max_servers:
            logger.warning(f'Server count exceeds maximum: {node_config.max_servers}')

    def create_battle_server(self, battle_id: str, config: dict = None) -> Optional[NodeBattleServer]:
        """创建新的战斗服务器"""
        with self._lock:
            if battle_id in self.servers:
                logger.warning(f'Battle server {battle_id} already exists')
                return self.servers[battle_id]

            # 检查服务器数量限制
            if len(self.servers) >= node_config.max_servers:
                raise NodeServerError(f'Maximum server limit reached: {node_config.max_servers}')

            # 分配端口（带重试机制和租约系统）
            port = None
            max_retries = settings.NODE_SERVER_MANAGER.get('PORT_ALLOCATION_MAX_RETRIES', 3)
            for attempt in range(max_retries):
                port = self._allocate_port_with_lease(battle_id)
                if port:
                    break
                if attempt < max_retries - 1:
                    logger.warning(f'端口分配失败，第 {attempt + 1} 次重试...')
                    retry_delay = settings.NODE_SERVER_MANAGER.get('PORT_ALLOCATION_RETRY_DELAY', 0.5)
                    time.sleep(retry_delay)

            if not port:
                raise NodeServerError('无法分配可用端口')

            try:
                # 创建服务器实例
                server = NodeBattleServer(battle_id, port, config)
                self.servers[battle_id] = server
                logger.info(f'Created battle server {battle_id} on port {port}')
                return server

            except Exception as e:
                # 创建失败时释放端口租约
                self._release_port_lease(port)
                logger.error(f'Failed to create battle server {battle_id}: {e}')
                raise NodeServerError(f'创建服务器失败: {e}')

    def start_battle_server(self, battle_id: str) -> bool:
        """启动战斗服务器"""
        with self._lock:
            server = self.servers.get(battle_id)
            if not server:
                logger.error(f'Battle server {battle_id} not found')
                return False

            try:
                return server.start()
            except NodeServerError as e:
                logger.error(f'Failed to start battle server {battle_id}: {e}')
                return False

    def stop_battle_server(self, battle_id: str) -> bool:
        """停止战斗服务器"""
        with self._lock:
            server = self.servers.get(battle_id)
            if not server:
                logger.warning(f'Battle server {battle_id} not found')
                return True

            try:
                success = server.stop()

                # 释放端口租约
                self._release_port_lease(server.port)

                # 释放端口（兼容旧系统）
                if server.port in self.used_ports:
                    self.used_ports.remove(server.port)

                # 移除服务器实例
                del self.servers[battle_id]

                logger.info(f'Battle server {battle_id} removed from manager')
                return success
            except Exception as e:
                logger.error(f'Error stopping battle server {battle_id}: {e}')
                return False

    def get_server_status(self, battle_id: str) -> Optional[dict]:
        """获取服务器状态"""
        with self._lock:
            server = self.servers.get(battle_id)
            if not server:
                return None

            try:
                return server.get_status()
            except Exception as e:
                logger.error(f'Error getting status for {battle_id}: {e}')
                return None

    def list_servers(self) -> List[dict]:
        """列出所有服务器"""
        with self._lock:
            servers_status = []
            for server in self.servers.values():
                try:
                    status = server.get_status()
                    servers_status.append(status)
                except Exception as e:
                    logger.error(f'Error getting status for server {server.battle_id}: {e}')
                    # 添加错误状态
                    servers_status.append(
                        {'battle_id': server.battle_id, 'port': server.port, 'status': 'error', 'error_message': str(e)}
                    )
            return servers_status

    def cleanup_unhealthy_servers(self) -> int:
        """清理不健康的服务器"""
        with self._lock:
            unhealthy_servers = []

            for battle_id, server in list(self.servers.items()):
                try:
                    if not server.is_healthy():
                        unhealthy_servers.append(battle_id)
                except Exception as e:
                    logger.error(f'Error checking health for {battle_id}: {e}')
                    unhealthy_servers.append(battle_id)

            for battle_id in unhealthy_servers:
                logger.warning(f'Cleaning up unhealthy server {battle_id}')
                try:
                    self.stop_battle_server(battle_id)
                except Exception as e:
                    logger.error(f'Error cleaning up server {battle_id}: {e}')

            if unhealthy_servers:
                logger.info(f'Cleaned up {len(unhealthy_servers)} unhealthy servers')

            return len(unhealthy_servers)

    def stop_all_servers(self) -> bool:
        """停止所有服务器"""
        with self._lock:
            battle_ids = list(self.servers.keys())
            success_count = 0

            for battle_id in battle_ids:
                try:
                    if self.stop_battle_server(battle_id):
                        success_count += 1
                except Exception as e:
                    logger.error(f'Error stopping server {battle_id}: {e}')

            logger.info(f'Stopped {success_count}/{len(battle_ids)} servers')
            return success_count == len(battle_ids)

    def _allocate_port_with_lease(self, battle_id: str) -> Optional[int]:
        """使用租约系统分配端口"""
        with self._lease_lock:
            # 定期清理过期租约
            self._cleanup_expired_leases()

            port_range = node_config.port_range

            for port in range(port_range[0], port_range[1] + 1):
                # 检查端口是否已被租约占用
                if port in self.port_leases:
                    lease = self.port_leases[port]
                    if not lease.is_expired and lease.is_active:
                        logger.debug(f'端口 {port} 已被战斗 {lease.battle_id} 租用，剩余时间: {lease.remaining_time}秒')
                        continue
                    else:
                        # 清理过期或无效的租约
                        logger.info(f'清理端口 {port} 的过期租约 (战斗: {lease.battle_id})')
                        del self.port_leases[port]

                # 检查端口是否在旧的使用列表中
                if port in self.used_ports:
                    logger.debug(f'端口 {port} 在旧的使用列表中，跳过')
                    continue

                # 检查端口是否真的可用
                if self._is_port_truly_available(port):
                    # 创建端口租约
                    lease = PortLease(port, battle_id, node_config.port_lease_duration)
                    self.port_leases[port] = lease
                    self.used_ports.add(port)

                    logger.info(f'成功为战斗 {battle_id} 分配端口 {port}，租约时长: {node_config.port_lease_duration}秒')
                    return port
                else:
                    logger.debug(f'端口 {port} 被其他进程占用，跳过')
                    continue

            # 如果所有端口都被占用，尝试清理一些端口
            logger.warning('所有端口都被占用，尝试清理占用的端口...')
            cleaned_count = self.cleanup_port_processes()
            if cleaned_count > 0:
                logger.info(f'清理了 {cleaned_count} 个端口，重新尝试分配')
                # 重新尝试分配（递归调用，但只尝试一次）
                if not hasattr(self, '_allocation_retry'):
                    self._allocation_retry = True
                    try:
                        return self._allocate_port_with_lease(battle_id)
                    finally:
                        delattr(self, '_allocation_retry')

            return None

    def _release_port_lease(self, port: int) -> bool:
        """释放端口租约"""
        with self._lease_lock:
            if port in self.port_leases:
                lease = self.port_leases[port]
                lease.release()
                del self.port_leases[port]
                self.used_ports.discard(port)
                logger.info(f'释放端口 {port} 的租约 (战斗: {lease.battle_id})')
                return True
            return False

    def _cleanup_expired_leases(self):
        """清理过期的端口租约"""
        current_time = datetime.now()

        # 检查是否需要清理（避免频繁清理）
        if (current_time - self._last_lease_cleanup).total_seconds() < node_config.port_lease_cleanup_interval:
            return

        with self._lease_lock:
            expired_ports = []
            for port, lease in self.port_leases.items():
                if lease.is_expired or not lease.is_active:
                    expired_ports.append(port)

            for port in expired_ports:
                lease = self.port_leases[port]
                logger.info(f'清理过期的端口租约: 端口 {port}, 战斗 {lease.battle_id}')
                del self.port_leases[port]
                self.used_ports.discard(port)

            self._last_lease_cleanup = current_time

            if expired_ports:
                logger.info(f'清理了 {len(expired_ports)} 个过期的端口租约')

    def _renew_port_lease(self, battle_id: str, duration: int = None) -> bool:
        """续租端口"""
        if duration is None:
            duration = node_config.port_lease_duration

        with self._lease_lock:
            for port, lease in self.port_leases.items():
                if lease.battle_id == battle_id and lease.is_active:
                    lease.renew(duration)
                    logger.info(f'为战斗 {battle_id} 续租端口 {port}，新的租约时长: {duration}秒')
                    return True
            return False

    def get_port_lease_info(self, battle_id: str) -> Optional[dict]:
        """获取端口租约信息"""
        with self._lease_lock:
            for port, lease in self.port_leases.items():
                if lease.battle_id == battle_id and lease.is_active:
                    return {
                        'port': port,
                        'battle_id': battle_id,
                        'lease_start': lease.lease_start.isoformat(),
                        'remaining_time': lease.remaining_time,
                        'is_expired': lease.is_expired
                    }
            return None

    def _allocate_port(self) -> Optional[int]:
        """分配可用端口（线程安全）- 保留旧方法以兼容性"""
        port_range = node_config.port_range

        # 使用更强的端口检测机制
        for port in range(port_range[0], port_range[1] + 1):
            # 原子性检查和分配
            if port not in self.used_ports:
                # 立即标记端口为已使用，防止并发分配
                self.used_ports.add(port)

                # 然后检查端口是否真的可用
                if self._is_port_truly_available(port):
                    logger.info(f'成功分配端口 {port}')
                    return port
                else:
                    # 端口被占用，保持标记并跳过
                    logger.debug(f'端口 {port} 被占用，跳过')
                    continue
            else:
                logger.debug(f'端口 {port} 已在使用列表中，跳过')

        # 如果所有端口都被占用，尝试清理一些端口
        logger.warning('所有端口都被占用，尝试清理占用的端口...')
        cleaned_count = self.cleanup_port_processes()
        if cleaned_count > 0:
            logger.info(f'清理了 {cleaned_count} 个端口，重新尝试分配')
            # 重新尝试分配（递归调用，但只尝试一次）
            if not hasattr(self, '_allocation_retry'):
                self._allocation_retry = True
                try:
                    return self._allocate_port()
                finally:
                    delattr(self, '_allocation_retry')

        return None

    def _is_port_truly_available(self, port: int) -> bool:
        """更严格的端口可用性检查"""
        import socket

        # 方法1: 尝试绑定端口
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
                sock.bind(('localhost', port))
                sock.listen(1)
                # 端口可以绑定，说明可用
        except OSError:
            logger.debug(f'端口 {port} 绑定失败，被占用')
            return False

        # 方法2: 检查是否有进程在监听该端口
        try:
            import psutil
            for conn in psutil.net_connections():
                if conn.laddr.port == port and conn.status == psutil.CONN_LISTEN:
                    logger.debug(f'端口 {port} 被进程 {conn.pid} 监听')
                    return False
        except Exception as e:
            logger.debug(f'检查端口 {port} 监听状态时出错: {e}')

        # 方法3: 尝试连接端口（检查是否有服务在运行）
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
                sock.settimeout(0.1)  # 很短的超时时间
                result = sock.connect_ex(('localhost', port))
                if result == 0:
                    logger.debug(f'端口 {port} 有服务在运行')
                    return False
        except Exception:
            pass

        return True

    def force_release_port(self, port: int) -> bool:
        """强制释放指定端口"""
        with self._lock:
            try:
                # 从used_ports中移除
                self.used_ports.discard(port)

                # 检查是否有对应的服务器实例
                server_to_remove = None
                for battle_id, server in self.servers.items():
                    if server.port == port:
                        server_to_remove = battle_id
                        break

                if server_to_remove:
                    # 停止并移除服务器
                    server = self.servers[server_to_remove]
                    server.stop()
                    del self.servers[server_to_remove]
                    logger.info(f'强制释放端口 {port}，移除服务器 {server_to_remove}')
                else:
                    logger.info(f'强制释放端口 {port}')

                # 尝试清理端口上的进程
                self._cleanup_port_process(port)

                return True
            except Exception as e:
                logger.error(f'强制释放端口 {port} 失败: {e}')
                return False

    def _update_used_ports(self):
        """更新已占用端口列表，移除不再使用的端口"""
        import socket

        # 检查当前标记为已使用的端口是否真的还在使用
        ports_to_remove = set()

        # 首先检查我们管理的服务器端口
        managed_ports = {server.port for server in self.servers.values()}

        for port in list(self.used_ports):
            # 如果是我们管理的服务器端口，检查服务器状态
            if port in managed_ports:
                # 找到对应的服务器
                server = next((s for s in self.servers.values() if s.port == port), None)
                if server and server.status in ['stopped', 'error']:
                    # 服务器已停止，可以释放端口
                    ports_to_remove.add(port)
                    logger.debug(f'释放已停止服务器的端口 {port}')
                continue

            # 对于非管理的端口，使用更严格的检查
            if self._is_port_truly_available(port):
                ports_to_remove.add(port)
                logger.debug(f'释放不再使用的端口 {port}')

        # 移除不再使用的端口
        if ports_to_remove:
            self.used_ports -= ports_to_remove
            logger.debug(f'释放了 {len(ports_to_remove)} 个端口: {sorted(ports_to_remove)}')
    def _cleanup_orphaned_processes(self):
        """清理系统中残留的Node.js战斗服务器进程"""
        try:
            logger.info('开始清理残留的Node.js战斗服务器进程...')

            # 首先清理占用端口范围的进程
            port_cleaned = self.cleanup_port_processes()

            cleaned_count = 0

            # 遍历所有进程，查找Node.js战斗服务器进程
            for proc in psutil.process_iter(['pid', 'name', 'cmdline']):
                try:
                    proc_info = proc.info
                    if not proc_info['cmdline']:
                        continue

                    cmdline = ' '.join(proc_info['cmdline'])

                    # 检查是否是Node.js战斗服务器进程
                    # 匹配包含server.js和端口范围的进程
                    if (
                        'node' in proc_info['name'].lower()
                        and 'server.js' in cmdline
                        and any(
                            str(port) in cmdline for port in range(node_config.port_range[0], node_config.port_range[1] + 1)
                        )
                    ):
                        logger.warning(f'发现残留的Node.js战斗服务器进程: PID={proc_info["pid"]}, CMD={cmdline}')

                        # 尝试优雅地终止进程
                        try:
                            proc.terminate()
                            proc.wait(timeout=5)
                            cleaned_count += 1
                            logger.info(f'成功终止残留进程: PID={proc_info["pid"]}')
                        except psutil.TimeoutExpired:
                            # 如果优雅终止失败，强制杀死进程
                            try:
                                proc.kill()
                                proc.wait(timeout=2)
                                cleaned_count += 1
                                logger.info(f'强制终止残留进程: PID={proc_info["pid"]}')
                            except Exception as e:
                                logger.error(f'无法终止进程 PID={proc_info["pid"]}: {e}')
                        except Exception as e:
                            logger.error(f'终止进程时出错 PID={proc_info["pid"]}: {e}')

                except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                    # 进程可能已经不存在或无权限访问
                    continue
                except Exception as e:
                    logger.debug(f'检查进程时出错: {e}')
                    continue

            total_cleaned = port_cleaned + cleaned_count
            if total_cleaned > 0:
                logger.info(
                    f'清理完成，共终止了 {total_cleaned} 个残留的Node.js战斗服务器进程（端口清理: {port_cleaned}, 进程清理: {cleaned_count}）'
                )
            else:
                logger.info('未发现残留的Node.js战斗服务器进程')

        except Exception as e:
            logger.error(f'清理残留进程时出错: {e}')

    def cleanup_port_processes(self) -> int:
        """清理占用端口范围内的所有进程"""
        cleaned_count = 0
        try:
            logger.info(f'开始清理端口范围 {node_config.port_range[0]}-{node_config.port_range[1]} 内的进程...')

            for port in range(node_config.port_range[0], node_config.port_range[1] + 1):
                try:
                    # 查找占用该端口的进程
                    for conn in psutil.net_connections(kind='inet'):
                        if conn.laddr.port == port and conn.status == psutil.CONN_LISTEN:
                            try:
                                proc = psutil.Process(conn.pid)
                                proc_info = proc.as_dict(['pid', 'name', 'cmdline'])

                                # 检查是否是Node.js进程
                                if 'node' in proc_info['name'].lower():
                                    logger.warning(
                                        f'发现占用端口 {port} 的Node.js进程: PID={conn.pid}, CMD={" ".join(proc_info["cmdline"]) if proc_info["cmdline"] else "N/A"}'
                                    )

                                    # 尝试终止进程
                                    try:
                                        proc.terminate()
                                        proc.wait(timeout=5)
                                        cleaned_count += 1
                                        logger.info(f'成功终止占用端口 {port} 的进程: PID={conn.pid}')
                                    except psutil.TimeoutExpired:
                                        try:
                                            proc.kill()
                                            proc.wait(timeout=2)
                                            cleaned_count += 1
                                            logger.info(f'强制终止占用端口 {port} 的进程: PID={conn.pid}')
                                        except Exception as e:
                                            logger.error(f'无法终止占用端口 {port} 的进程 PID={conn.pid}: {e}')
                                    except Exception as e:
                                        logger.error(f'终止占用端口 {port} 的进程时出错 PID={conn.pid}: {e}')

                            except (psutil.NoSuchProcess, psutil.AccessDenied):
                                continue

                except Exception:
                    continue

            if cleaned_count > 0:
                logger.info(f'端口清理完成，共终止了 {cleaned_count} 个占用端口的进程')
            else:
                logger.info('未发现占用指定端口范围的Node.js进程')

        except Exception as e:
            logger.error(f'清理端口进程时出错: {e}')

        return cleaned_count

    def _cleanup_port_process(self, port: int) -> bool:
        """清理占用指定端口的进程"""
        try:
            # 查找占用该端口的进程
            for conn in psutil.net_connections(kind='inet'):
                if conn.laddr.port == port and conn.status == psutil.CONN_LISTEN:
                    try:
                        proc = psutil.Process(conn.pid)
                        proc_info = proc.as_dict(['pid', 'name', 'cmdline'])

                        # 检查是否是Node.js进程
                        if 'node' in proc_info['name'].lower():
                            logger.warning(
                                f'发现占用端口 {port} 的Node.js进程: PID={conn.pid}, CMD={" ".join(proc_info["cmdline"]) if proc_info["cmdline"] else "N/A"}'
                            )

                            # 尝试终止进程
                            try:
                                proc.terminate()
                                proc.wait(timeout=3)
                                logger.info(f'成功终止占用端口 {port} 的进程: PID={conn.pid}')
                                return True
                            except psutil.TimeoutExpired:
                                try:
                                    proc.kill()
                                    proc.wait(timeout=2)
                                    logger.info(f'强制终止占用端口 {port} 的进程: PID={conn.pid}')
                                    return True
                                except Exception as e:
                                    logger.error(f'无法终止占用端口 {port} 的进程 PID={conn.pid}: {e}')
                                    return False
                            except Exception as e:
                                logger.error(f'终止占用端口 {port} 的进程时出错 PID={conn.pid}: {e}')
                                return False

                    except (psutil.NoSuchProcess, psutil.AccessDenied):
                        continue

            return False
        except Exception as e:
            logger.error(f'清理端口 {port} 进程时出错: {e}')
            return False

    def get_stats(self) -> dict:
        """获取管理器统计信息"""
        with self._lock:
            stats = {
                'total_servers': len(self.servers),
                'running_servers': 0,
                'stopped_servers': 0,
                'error_servers': 0,
                'used_ports': len(self.used_ports),
                'available_ports': node_config.port_range[1] - node_config.port_range[0] + 1 - len(self.used_ports),
            }

            for server in self.servers.values():
                if server.status == 'running':
                    stats['running_servers'] += 1
                elif server.status == 'stopped':
                    stats['stopped_servers'] += 1
                else:
                    stats['error_servers'] += 1

            return stats


# 全局服务器管理器实例
node_server_manager = NodeServerManager()


def get_node_server_manager() -> NodeServerManager:
    """获取全局服务器管理器实例"""
    return node_server_manager
